package com.microservices.yucloud.appweb.service;

import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.io.Input;
import com.esotericsoftware.kryo.io.Output;
import com.microservices.yucloud.appweb.contants.BizOrderConstants;
import com.microservices.yucloud.appweb.domain.BizOrderStateMachineContext;
import com.microservices.yucloud.appweb.enums.BizOrderEventsEnum;
import com.microservices.yucloud.appweb.enums.BizOrderStatusEnum;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.messaging.MessageHeaders;
import org.springframework.statemachine.StateMachineContext;
import org.springframework.statemachine.StateMachinePersist;
import org.springframework.statemachine.data.redis.RedisStateMachineContextRepository;
import org.springframework.statemachine.kryo.MessageHeadersSerializer;
import org.springframework.statemachine.kryo.StateMachineContextSerializer;
import org.springframework.statemachine.kryo.UUIDSerializer;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.util.Base64;
import java.util.Date;
import java.util.UUID;

/**
 * @author zy
 * @date 2019-09-03 15:02
 **/
@Component
public class BizOrderRedisStateMachineContextPersist implements StateMachinePersist<BizOrderStatusEnum, BizOrderEventsEnum, String> {

    @Autowired
    private RedisStateMachineContextRepository<BizOrderStatusEnum, BizOrderEventsEnum> redisStateMachineContextRepository;

    /*@Autowired
    private BizOrderStateMachineContextRepository bizOrderStateMachineContextRepository;*/

    //  加入存储到DB的数据repository, biz_order_state_machine_context表结构：
    //  bizOrderId
    //  contextStr
    //  curStatus
    //  updateTime

    /**
     * Write a {@link StateMachineContext} into a persistent store
     * with a context object {@code T}.
     *
     * @param context the context
     * @param orderId the context ojb
     */
    @Override
    @Transactional
    public void write(StateMachineContext<BizOrderStatusEnum, BizOrderEventsEnum> context, String orderId) {

        redisStateMachineContextRepository.save(context, orderId);
        //  save to db
        /*BizOrderStateMachineContext result = bizOrderStateMachineContextRepository.selectByOrderId(orderId);

        if (null == result) {
            BizOrderStateMachineContext orderContext = new BizOrderStateMachineContext(orderId,
                    context.getState().getStatus(), serialize(context), new Date());
            bizOrderStateMachineContextRepository.insertSelective(orderContext);
        } else {
            result.setCurOrderStatus(context.getState().getStatus());
            result.setContext(serialize(context));
            bizOrderStateMachineContextRepository.updateByPrimaryKeySelective(result);
        }*/
    }

    /**
     * Read a {@link StateMachineContext} from a persistent store
     * with a context object {@code T}.
     *
     * @param contextObj the context ojb
     * @return the state machine context
     */
    @Override
    public StateMachineContext<BizOrderStatusEnum, BizOrderEventsEnum> read(String contextObj) {

        StateMachineContext<BizOrderStatusEnum, BizOrderEventsEnum> context = redisStateMachineContextRepository.getContext(contextObj);
        //redis 访缓存击穿
        if (null != context && BizOrderConstants.STATE_MACHINE_CONTEXT_ISNULL.equalsIgnoreCase(context.getId())) {
            return null;
        }
        //redis 为空走db
        /*if (null == context) {
            BizOrderStateMachineContext bizOrderStateMachineContext = bizOrderStateMachineContextRepository.selectByOrderId(contextObj);
            if (null != bizOrderStateMachineContext) {
                context = deserialize(bizOrderStateMachineContext.getContext());
                redisStateMachineContextRepository.save(context, contextObj);
            } else {
                context = new StateMachineContextIsNull();
                redisStateMachineContextRepository.save(context, contextObj);
            }
        }*/
        return context;
    }

    private String serialize(StateMachineContext<BizOrderStatusEnum, BizOrderEventsEnum> context) {
        Kryo kryo = kryoThreadLocal.get();
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        Output output = new Output(out);
        kryo.writeObject(output, context);
        output.close();
        return Base64.getEncoder().encodeToString(out.toByteArray());
    }

    @SuppressWarnings("unchecked")
    private StateMachineContext<BizOrderStatusEnum, BizOrderEventsEnum> deserialize(String data) {
        if (StringUtils.isEmpty(data)) {
            return null;
        }
        Kryo kryo = kryoThreadLocal.get();
        ByteArrayInputStream in = new ByteArrayInputStream(Base64.getDecoder().decode(data));
        Input input = new Input(in);
        return kryo.readObject(input, StateMachineContext.class);
    }

    private static final ThreadLocal<Kryo> kryoThreadLocal = ThreadLocal.withInitial(() -> {
        Kryo kryo = new Kryo();
        kryo.addDefaultSerializer(StateMachineContext.class, new StateMachineContextSerializer());
        kryo.addDefaultSerializer(MessageHeaders.class, new MessageHeadersSerializer());
        kryo.addDefaultSerializer(UUID.class, new UUIDSerializer());
        return kryo;
    });
}
